﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Text;
using Tao.OpenGl;

namespace Ommf
{
    public class TrueTypeFont : IPrintable
    {
        private float pointSize;
        private ImageFilter filter;
        private Encoding encoding = Encoding.GetEncoding(437);

        private int[] glTextures = new int[256];
        private int glListBase;

        private int[] glyphWidths = new int[256];
        private int glyphHeight = 0;

        private bool isDisposed = false;

        internal TrueTypeFont(string filename, float pointSize, ImageFilter filter)
        {
            this.pointSize = pointSize;
            this.filter = filter;

            MakeGlyphTextures(filename);
            MakeLists();
        }

        public void Dispose()
        {
            Gl.glDeleteLists(glListBase, 256);
            Gl.glDeleteTextures(256, glTextures);
            isDisposed = true;
        }

        private void MakeGlyphTextures(string filename)
        {
            PrivateFontCollection pfc = new PrivateFontCollection();
            pfc.AddFontFile(filename);

            Font font = new Font(pfc.Families[0], pointSize);
            Bitmap bmp = new Bitmap(1, 1); //dummy bitmap so that we can use Graphics class
            Graphics g = Graphics.FromImage(bmp);
            g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

            Gl.glGenTextures(256, glTextures);

            for (int i = 0; i < 256; ++i)
            {
                SizeF glyphSize = g.MeasureString(((char)i).ToString(), font);
                if (i == 32) glyphSize.Width = pointSize - 1; //handle space character
                
                glyphWidths[i] = (int)glyphSize.Width;
                if (glyphSize.Height > glyphHeight) glyphHeight = (int)glyphSize.Height;

                Bitmap glyphBitmap = new Bitmap((int)glyphSize.Width, (int)glyphSize.Height);

                Graphics glyphGraph = Graphics.FromImage(glyphBitmap);
                glyphGraph.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
                glyphGraph.Clear(Color.Transparent);
                glyphGraph.DrawString(((char)i).ToString(), font, Brushes.White, new PointF(0, 0));
                glyphGraph.Dispose();

                BitmapData bmpData = glyphBitmap.LockBits(new Rectangle(0, 0, glyphBitmap.Width, glyphBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                Gl.glBindTexture(Gl.GL_TEXTURE_2D, glTextures[i]);

                int glFilter = Gl.GL_LINEAR;
                switch (filter)
                {
                    case ImageFilter.Linear: glFilter = Gl.GL_LINEAR; break;
                    case ImageFilter.Nearest: glFilter = Gl.GL_NEAREST; break;
                }

                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, glFilter);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, glFilter);

                Gl.glTexImage2D(
                    Gl.GL_TEXTURE_2D, 0, 4, bmpData.Width, bmpData.Height,
                    0, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, bmpData.Scan0);

                glyphBitmap.UnlockBits(bmpData);
                glyphBitmap.Dispose();
            }

            g.Dispose();
            bmp.Dispose();
            font.Dispose();
            pfc.Dispose();
        }

        private void MakeLists()
        {
            glListBase = Gl.glGenLists(256);

            for (int i = 0; i < 256; ++i)
            {
                Gl.glNewList(glListBase + i, Gl.GL_COMPILE);

                if (i != 0x0A) // newline
                {
                    Gl.glBindTexture(Gl.GL_TEXTURE_2D, glTextures[i]);

                    Gl.glBegin(Gl.GL_QUADS);

                    Gl.glTexCoord2f(0.0f, 0.0f); Gl.glVertex3f(0, 0, 0);
                    Gl.glTexCoord2f(0.0f, 1.0f); Gl.glVertex3f(0, glyphHeight, 0);
                    Gl.glTexCoord2f(1.0f, 1.0f); Gl.glVertex3f(glyphWidths[i], glyphHeight, 0);
                    Gl.glTexCoord2f(1.0f, 0.0f); Gl.glVertex3f(glyphWidths[i], 0, 0);

                    Gl.glEnd();

                    Gl.glTranslatef(glyphWidths[i] - (pointSize / 2), 0, 0);
                }
                else
                {
                    Gl.glPopMatrix();
                    Gl.glTranslatef(0, glyphHeight, 0);
                    Gl.glPushMatrix();
                }

                Gl.glEndList();
            }
        }

        #region IPrintable Members

        public void Print(string text, float x, float y, float angle, float scaleX, float scaleY)
        {
            Gl.glPushMatrix();

            Gl.glTranslatef(x, y, 0);
            Gl.glRotatef(angle, 0, 0, 1);
            Gl.glScalef(scaleX, scaleY, 1.0f);

            Gl.glPushMatrix();
            Gl.glListBase(glListBase);
            Gl.glCallLists(text.Length, Gl.GL_UNSIGNED_BYTE, encoding.GetBytes(text));
            Gl.glPopMatrix();

            Gl.glPopMatrix();
        }

        #endregion

        public bool IsDisposed
        {
            get { return isDisposed; }
        }
    }
}
